﻿#region Copyright © 2011 Prageeth Silva <prageeth@thenewcoders.com>
/*
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the author(s) be held liable for any damages arising from
 * the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 *   1. The origin of this software must not be misrepresented; you must not
 *      claim that you wrote the original software. If you use this software
 *      in a product, an acknowledgment in the product documentation would be
 *      appreciated but is not required.
 * 
 *   2. Altered source versions must be plainly marked as such, and must not
 *      be misrepresented as being the original software.
 * 
 *   3. This notice may not be removed or altered from any source distribution.
 */
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Windows.Forms;
using iTextSharp.text;
using SourcePlus.Core;
using SourcePlus.Formatters;
using SourcePlus.RtfPdf;
using SourcePlus.Themes;

namespace SourcePlus.Ui
{
    public partial class MainForm : Form
    {

        private const string BatchPrint = "BatchPrint";
        private const string PdfMerge = "PDF Merge";

        private PdfMerger merger = new PdfMerger();

        private BatchAssembler assembler = new BatchAssembler();
        
        private ToolTip tip = new ToolTip();

        private SourceTheme selectedTheme = null;
        public SourceTheme SelectedTheme 
        {
            get { return this.selectedTheme; }
            set
            {
                this.selectedTheme = value;
                if (value != null) { this.btnPrintTheme.Text = value.ToString(); }
            }
        }

        public MainForm()
        {

            InitializeComponent();
             
            this.tip.AutoPopDelay = 5000;
            this.tip.InitialDelay = 500;
            this.tip.ReshowDelay = 500;
                        
            this.InitialiseMergeSection();
            this.InitialiseBatchSection();
            
            this.HideProgressAndMessage();

            frmSplash splash = new frmSplash(this);
            splash.ShowDialog(this);
            
        }

        #region "UI Helpers"

        private void ShowProgress(int percentage)
        {
            this.statusProgress.Value = percentage;
            this.statusProgress.Visible = true;
        }

        private void ShowProgress(int percentage, string message)
        {
            this.ShowProgress(percentage);
            this.ShowMessage(message);
        }

        private void ShowProgressSuccess(int percentage, string message)
        {
            this.ShowProgress(percentage);
            this.ShowMessageSuccess(message);
        }

        private void ShowProgressError(int percentage, string message)
        {
            this.ShowProgress(percentage);
            this.ShowMessageError(message);
        }

        private void ShowMessage(string message)
        {
            this.statusMsg.Text = message;
            this.statusMsg.ForeColor = System.Drawing.Color.Black;
            this.statusMsg.Visible = true;
        }

        private void ShowMessageSuccess(string message)
        {
            this.statusMsg.Text = message;
            this.statusMsg.ForeColor = System.Drawing.Color.Green;
            this.statusMsg.Visible = true;
        }

        private void ShowMessageError(string message)
        {
            this.statusMsg.Text = message;
            this.statusMsg.ForeColor = System.Drawing.Color.Red;
            this.statusMsg.Visible = true;
        }

        private void HideMessage()
        {
            this.statusMsg.Visible = false;
        }

        private void HideProgress()
        {
            this.statusProgress.Visible = false;
        }

        private void HideProgressAndMessage()
        {
            this.HideMessage();
            this.HideProgress();
        }

        #endregion
                
        #region "Merging"

        private void InitialiseMergeSection()
        {

            // callbacks

            this.merger.ProgressChanged += new ProgressChangedEventHandler(merger_ProgressChanged);
            this.merger.MergeCompleted += new MergeCompletedEventHandler(merger_MergeCompleted);

            // add tooltips
                        
            tip.SetToolTip(this.btnMerge, "Merge PDF files");
            tip.SetToolTip(this.btnMergeCancel, "Cancel current merge operation");
            tip.SetToolTip(this.btnMergeAddFolder, "Add folder containing PDF files");
            tip.SetToolTip(this.btnMergeAddFiles, "Add PDF files");
            tip.SetToolTip(this.btnMergeRemove, "Remove selected PDF files");
            tip.SetToolTip(this.btnMergeClear, "Clear all PDF files");
            tip.SetToolTip(this.btnMergeMoveUp, "Move the selected PDF files up");
            tip.SetToolTip(this.btnMergeMoveDown, "Move the selected PDF files down");
            tip.SetToolTip(this.btnMergeDesitnation, "Choose destination path");

        }
                
        private void btnMerge_Click(object sender, EventArgs e)
        {
            
            // get overwriting and skipping statuses
            this.merger.OverwriteDestination = this.chkMergeOverwrite.Checked;
            this.merger.SkipUnreadableFiles = this.chkMergeSkip.Checked;

            // gather the source list
            List<SourceFile> sources = new List<SourceFile>(this.listMerge.Items.Count);
            foreach (object obj in this.listMerge.Items)
            {
                SourceFile s = obj as SourceFile;
                if (s != null)
                {
                    sources.Add(s);
                }
            }

            // valid sources?
            if (sources.Count == 0)
            {
                MessageBox.Show("Please add atleast one source file!", PdfMerge, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }

            // valid destination?
            DestinationFile destination = new DestinationFile(this.txtMergeDestination.Text);
            if (destination.LongName.Length == 0)
            {
                MessageBox.Show("Please enter/select a destination file!", PdfMerge, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
            else
            {
                foreach (SourceFile s in sources)
                {
                    if (s.LongName.ToLower() == destination.LongName.ToLower())
                    {
                        MessageBox.Show("Destination cannot be included in the source list!" + Environment.NewLine +
                            "Either remove the file from the source list or select a new destination.", PdfMerge, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                        return;
                    }
                }
            }

            // destination exists? overwrite?
            if (File.Exists(destination.LongName) && !this.merger.OverwriteDestination)
            {
                if (MessageBox.Show("Destination file already exists!" + Environment.NewLine + "Overwrite file?", PdfMerge,
                    MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    this.chkMergeOverwrite.Checked = true;
                    this.merger.OverwriteDestination = true;
                }
                else
                {
                    return;
                }
            }

            this.btnMergeCancel.Enabled = true;
            this.btnMerge.Enabled = false;

            this.merger.MergeFiles(destination, sources.ToArray());

            this.ShowMessage("Merging " + sources.Count + " file(s)...");

        }

        private void btnMergeCancel_Click(object sender, EventArgs e)
        {
            this.merger.CancelMerging();            
        }

        private void merger_MergeCompleted(object sender, MergeCompletedEventArgs e)
        {

            PdfMerger merger = sender as PdfMerger;
            //this.HideProgress();
            if (e.Cancelled)
            {
                this.ShowMessageError("Merging cancelled by user.");
            }
            else if (e.Error != null)
            {
                this.ShowMessageError("Merging failed: " + e.Error.Message);
            }
            else if (merger.Failed > 0)
            {
                this.ShowMessageSuccess("Merging completed with " + merger.Failed + " file(s) failing.");
            }
            else
            {
                int? totalPages = (int?)e.UserState;
                if (totalPages.HasValue)
                {
                    this.ShowMessageSuccess("Merging " + merger.Successful + " file(s) with " + totalPages.Value + " completed successfully!");
                }
                else
                {
                    this.ShowMessageSuccess("Merging " + merger.Successful + " file(s) completed successfully!");
                }
            }

            this.btnMergeCancel.Enabled = false;
            this.btnMerge.Enabled = true;

        }

        private void merger_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.ShowProgress(e.ProgressPercentage);
        }

        private void btnMergeAddFolder_Click(object sender, EventArgs e)
        {

            FolderBrowserDialog dlg = new FolderBrowserDialog();
            dlg.Description = "Select the folder that contains PDF files:";
            dlg.ShowNewFolderButton = false;

            if (dlg.ShowDialog(this) == DialogResult.OK)
            {

                string path = dlg.SelectedPath;
                string[] files = null;
                if (Directory.Exists(path) &&
                    (files = Directory.GetFiles(path, "*.pdf", SearchOption.TopDirectoryOnly)).Length > 0)
                {

                    this.listMerge.SuspendLayout();
                    foreach (string f in files)
                    {
                        this.listMerge.Items.Add(new SourceFile(f));
                    }
                    this.listMerge.ResumeLayout();

                    this.UpdateMergeSourceTotal();

                }
                else
                {
                    MessageBox.Show("No PDF files were found in the specified path!", PdfMerge, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }

                this.UpdateMergeSourceTotal();

            }

        }

        private void btnMergeAddFiles_Click(object sender, EventArgs e)
        {

            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Title = "Source files";
            dlg.Filter = "PDF files (*.pdf)|*.pdf|All files (*.*)|*.*";
            dlg.FilterIndex = 1;
            dlg.CheckPathExists = true;
            dlg.CheckFileExists = true;
            dlg.Multiselect = true;

            if (dlg.ShowDialog(this) == DialogResult.OK)
            {

                this.listMerge.SuspendLayout();
                foreach (string f in dlg.FileNames)
                {                    
                    this.listMerge.Items.Add(new SourceFile(f));
                }
                this.listMerge.ResumeLayout();

                this.UpdateMergeSourceTotal();
            }

        }
        
        private void btnMergeRemove_Click(object sender, EventArgs e)
        {

            ListBox.SelectedIndexCollection ic = this.listMerge.SelectedIndices;

            if (ic.Count > 0)
            {

                // get the selected indices
                int[] indices = new int[ic.Count];
                int i = ic.Count - 1;
                foreach (int idx in ic)
                {
                    indices[i] = idx;
                    i--;
                }

                this.listMerge.SuspendLayout();
 
                foreach (int idx in indices)
                {
                    this.listMerge.Items.RemoveAt(idx);
                }
                
                this.listMerge.ResumeLayout();

                this.UpdateMergeSourceTotal();

            }

        }
        
        private void btnMergeClear_Click(object sender, EventArgs e)
        {
            
            if (this.listMerge.Items.Count > 0)
            {
                if (MessageBox.Show("Clear the source list?", PdfMerge,
                    MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {

                    this.listMerge.SuspendLayout();

                    this.listMerge.Items.Clear();

                    this.listMerge.ResumeLayout();

                    this.UpdateMergeSourceTotal();

                }
            }

        }

        private void btnMergeDesitnation_Click(object sender, EventArgs e)
        {

            SaveFileDialog dlg = new SaveFileDialog();
            dlg.Title = "Merged destination file";
            dlg.Filter = "PDF files (*.pdf)|*.pdf|All files (*.*)|*.*";
            dlg.FileName = "Merged.pdf";
            dlg.FilterIndex = 1;
            dlg.CheckPathExists = true;
            
            if (dlg.ShowDialog(this) == DialogResult.OK)
            {
                this.txtMergeDestination.Text = dlg.FileName;
            }

        }

        private void btnMergeMoveUp_Click(object sender, EventArgs e)
        {
            UiHelper.MoveItemsUp(this.listMerge);            
        }

        private void btnMergeMoveDown_Click(object sender, EventArgs e)
        {
            UiHelper.MoveItemsDown(this.listMerge);
        }

        private void UpdateMergeSourceTotal()
        {
            this.lblMergeTotal.Text = "Total: " + this.listMerge.Items.Count;
        }

        #endregion
                
        #region "Batch Printer"

        private void InitialiseBatchSection()
        {

            // callbacks
            this.assembler.ProgressChanged += new ProgressChangedEventHandler(assembler_ProgressChanged);
            this.assembler.AssemblyCompleted += new AssemblyCompletedEventHandler(assembler_AssemblyCompleted);
            

            // add tooltips
                        
            tip.SetToolTip(this.btnPrint, "Print files to PDF");
            tip.SetToolTip(this.btnPrintCancel, "Cancel current printing operation");
            tip.SetToolTip(this.btnPrintClear, "Clear the list");
            tip.SetToolTip(this.btnPrintRemoveSelected, "Remove selected source files");
            tip.SetToolTip(this.btnPrintMoveUp, "Move the selected source files up");
            tip.SetToolTip(this.btnMergeMoveDown, "Move the selected source files down");
            tip.SetToolTip(this.btnPrintDestination, "Choose destination path");
            tip.SetToolTip(this.btnPrintTheme, "Change selected theme");

            // TODO add to other controls too.

            // initialise values
            this.SelectedTheme = null;

        }

        private void assembler_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {

            string msg = e.UserState as string;
            if (msg != null)
            {
                this.ShowProgress(e.ProgressPercentage, msg);
            }
            else
            {
                this.ShowProgress(e.ProgressPercentage);
            }

        }

        private void assembler_AssemblyCompleted(object sender, AssemblyCompletedEventArgs e)
        {

            BatchAssembler assembler = sender as BatchAssembler;
            //this.HideProgress();
            if (e.Cancelled)
            {
                this.ShowMessageError("Printing cancelled by user.");
            }
            else if (e.Error != null)
            {
                this.ShowMessageError("Printing failed: " + e.Error.Message);
            }
            else if (assembler.Failed > 0)
            {
                this.ShowMessageSuccess("Printing completed with " + assembler.Failed + " file(s) failing.");
            }
            else
            {
                int? totalPages = (int?)e.UserState;
                if (totalPages.HasValue)
                {
                    this.ShowMessageSuccess("Printing " + assembler.Successful + " file(s) with " + totalPages.Value + " completed successfully!");
                }
                else
                {
                    this.ShowMessageSuccess("Printing " + assembler.Successful + " file(s) completed successfully!");
                }
            }

            this.btnPrintCancel.Enabled = false;
            this.btnPrint.Enabled = true;

        }

        private void btnBatchTheme_Click(object sender, EventArgs e)
        {

            frmThemes themes = new frmThemes(this.SelectedTheme);

            if (themes.ShowDialog(this) == DialogResult.OK && themes.SelectedTheme != null)
            {
                this.SelectedTheme = themes.SelectedTheme;
                this.btnPrintTheme.Text = themes.SelectedTheme.ToString();
                this.ShowMessageSuccess("Theme changed to '" + this.btnPrintTheme.Text + "'");
            }

        }
        
        private void chkPrintProjectReplace_CheckedChanged(object sender, EventArgs e)
        {

            bool status = this.chkPrintProjReplace.Checked;

            this.lblPrintProjReplace.Enabled = status;
            this.txtPrintProjReplace.Enabled = status;
            this.lblPrintProjBy.Enabled = status;
            this.txtPrintProjBy.Enabled = status;

        }

        private void btnPrintProjectDir_Click(object sender, EventArgs e)
        {

            FolderBrowserDialog dlg = new FolderBrowserDialog();
            dlg.Description = "Select the project path:";
            dlg.ShowNewFolderButton = false;

            if (dlg.ShowDialog(this) == DialogResult.OK)
            {

                this.txtPrintProjPath.Text = dlg.SelectedPath;

            }

        }

        private void btnPrintProjScanTypes_Click(object sender, EventArgs e)
        {

            if (this.txtPrintProjPath.Text.Length == 0)
            {
                MessageBox.Show("Please enter/select the project directory!", BatchPrint, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }

            DirectoryAnalyser analyser = new DirectoryAnalyser();
            string[] types = analyser.GetFileTypes(this.txtPrintProjPath.Text, true, true);

            this.listFileTypes.SuspendLayout();
            this.listFileTypes.Items.Clear();
            foreach (string type in types)
            {
                this.listFileTypes.Items.Add(type);
            }
            this.listFileTypes.ResumeLayout();

        }

        private void btnPrintProjTypeClear_Click(object sender, EventArgs e)
        {

            if (this.listFileTypes.Items.Count > 0)
            {
                if (MessageBox.Show("Clear the file types list?", BatchPrint,
                    MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {

                    this.listFileTypes.SuspendLayout();

                    this.listFileTypes.Items.Clear();

                    this.listFileTypes.ResumeLayout();

                }
            }
            
        }

        private void btnPrintProjAdd_Click(object sender, EventArgs e)
        {
            string value = this.txtPrintProjTypesAdd.Text;
            if (value.Length > 0)
            {
                string lower = value.ToLower();
                foreach (object obj in this.listFileTypes.Items)
                {
                    if (lower == obj.ToString().ToLower()) { return; }
                }
                this.listFileTypes.Items.Add(lower);
            }

        }

        private void btnPrintProjLoadFiles_Click(object sender, EventArgs e)
        {

            if (this.txtPrintProjPath.Text.Length == 0)
            {
                MessageBox.Show("Please enter/select the project directory!", BatchPrint, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }

            string[] types = new string[this.listFileTypes.Items.Count];
            for (int i=0; i< types.Length; i++)
            {
                types[i] = this.listFileTypes.Items[i].ToString();
            }

            DirectoryAnalyser analyser = new DirectoryAnalyser();
            string[] files = analyser.GetFilesList(this.txtPrintProjPath.Text, types, true, true);

            this.listPrint.SuspendLayout();
            this.listPrint.Items.Clear();
            foreach (string file in files)
            {
                this.listPrint.Items.Add(new SourceFile(file));
            }
            this.listPrint.ResumeLayout();

            this.UpdatePrintSourceTotal();

        }

        private void UpdatePrintSourceTotal()
        {
            this.lblPrintTotal.Text = "Total: " + this.listPrint.Items.Count;
        }
        
        private void btnPrintRemoveSelected_Click(object sender, EventArgs e)
        {

            ListBox.SelectedIndexCollection ic = this.listPrint.SelectedIndices;

            if (ic.Count > 0)
            {

                // get the selected indices
                int[] indices = new int[ic.Count];
                int i = ic.Count - 1;
                foreach (int idx in ic)
                {
                    indices[i] = idx;
                    i--;
                }

                this.listPrint.SuspendLayout();

                foreach (int idx in indices)
                {
                    this.listPrint.Items.RemoveAt(idx);
                }

                this.listPrint.ResumeLayout();

                this.UpdatePrintSourceTotal();

            }

        }

        private void btnPrintClear_Click(object sender, EventArgs e)
        {

            if (this.listMerge.Items.Count > 0)
            {
                if (MessageBox.Show("Clear the source list?", BatchPrint,
                    MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {

                    this.listPrint.SuspendLayout();

                    this.listPrint.Items.Clear();

                    this.listPrint.ResumeLayout();

                    this.UpdatePrintSourceTotal();
                    
                }
            }

        }

        private void btnPrintMoveUp_Click(object sender, EventArgs e)
        {
            UiHelper.MoveItemsUp(this.listPrint);
        }

        private void btnPrintMoveDown_Click(object sender, EventArgs e)
        {
            UiHelper.MoveItemsDown(this.listPrint);
        }

        private void btnPrint_Click(object sender, EventArgs e)
        {
            
            // get directory overwriting
            if (this.chkPrintProjReplace.Checked)
            {
                this.assembler.TrimDirectory = this.txtPrintProjReplace.Text;
                this.assembler.AddDirectory = this.txtPrintProjBy.Text;
            }

            // get overwriting and skipping statuses
            this.assembler.OverwriteDestination = this.chkPrintOverwrite.Checked;
            this.assembler.SkipOnError = this.chkPrintSkip.Checked;
            this.assembler.WrapText = this.chkPrintWrap.Checked;
            if (this.assembler.WrapText)
            {
                int wraplen;
                if (!Int32.TryParse(this.txtPrintWrapLength.Text, out wraplen))
                {
                    MessageBox.Show("Please enter a valid wrap length!", BatchPrint, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    return;
                }
                this.assembler.WrapLength = wraplen;
            }
            
            // gather the source list
            List<SourceFile> sources = new List<SourceFile>(this.listPrint.Items.Count);
            foreach (object obj in this.listPrint.Items)
            {
                SourceFile s = obj as SourceFile;
                if (s != null)
                {
                    sources.Add(s);
                }
            }

            // valid sources?
            if (sources.Count == 0)
            {
                MessageBox.Show("Please add atleast one source file!", BatchPrint, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }

            // valid destination?
            DestinationFile destination = new DestinationFile(this.txtPrintDestination.Text);
            if (destination.LongName.Length == 0)
            {
                MessageBox.Show("Please enter/select a destination file!", BatchPrint, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
            else
            {
                foreach (SourceFile s in sources)
                {
                    if (s.LongName.ToLower() == destination.LongName.ToLower())
                    {
                        MessageBox.Show("Destination cannot be included in the source list!" + Environment.NewLine +
                            "Either remove the file from the source list or select a new destination.", BatchPrint, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                        return;
                    }
                }
            }

            // destination exists? overwrite?
            if (File.Exists(destination.LongName) && !this.assembler.OverwriteDestination)
            {
                if (MessageBox.Show("Destination file already exists!" + Environment.NewLine + "Overwrite file?", BatchPrint,
                    MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    this.chkPrintOverwrite.Checked = true;
                    this.assembler.OverwriteDestination = true;
                }
                else
                {
                    return;
                }
            }

            this.btnPrintCancel.Enabled = true;
            this.btnPrint.Enabled = false;

            this.assembler.Theme = this.SelectedTheme;

            this.assembler.FormatAndSaveSinglePdf(destination.LongName, sources.ToArray());

            this.ShowMessage("Printing " + sources.Count + " file(s) to single PDF...");

        }

        private void btnPrintDestination_Click(object sender, EventArgs e)
        {

            SaveFileDialog dlg = new SaveFileDialog();
            dlg.Title = "Combined destination file";
            dlg.Filter = "PDF files (*.pdf)|*.pdf|All files (*.*)|*.*";
            dlg.FileName = "Source.pdf";
            dlg.FilterIndex = 1;
            dlg.CheckPathExists = true;

            if (dlg.ShowDialog(this) == DialogResult.OK)
            {
                this.txtPrintDestination.Text = dlg.FileName;
            }

        }

        private void chkPrintWrap_CheckedChanged(object sender, EventArgs e)
        {
            bool status = this.chkPrintWrap.Checked;
            this.txtPrintWrapLength.Enabled = status;
            this.lblPrintWrap.Enabled = status;
        }

        private void btnPrintCancel_Click(object sender, EventArgs e)
        {
            this.assembler.CancelCurrentOperation();
        }
        
        #endregion

        private void MainForm_Load(object sender, EventArgs e)
        {

        }

    }
}
